L'IAT n'est pas une fin en soit. Il contient les noms des DLL et de leurs fonctions. Mais il faut bien que le programme consulte cette liste à un moment donné, et c'est dans la section .code que ça se passe. Quand vous utilisez une API dans votre code, ce code fait une liaison vers l'IAT pour accéder à cette fonction. Et forcément ce serait trop simple semble-t-il qu'il n'existe qu'un seul type de lien entre le code et l'IAT. Ce tutorial fait la lumière sur ce point.
Toutes les informations qui suivent sont le résultat d'observations. Il se peut donc que d'autres types de liens existent et ne soient pas décrits ici. Cependant je pense quand mêmes que la totalité des cas (Normaux) soient décrit ici, même si je ne peux pas le garantir à 100%. Je précise bien cas normaux parce qu'il est possible d'inventer de nouveaux types de liens, je pense en particulier à la façon dont Aspack crée des liens atypiques en évitant de pointer directement sur le début de l'adresse de la fonction qu'elle appelle.
Je pense qu'il exite quatre principaux types de liens:
I) APPEL DIRECT PROCHE |
II) APPEL DIRECT LOINTAIN
Call dword ptr [adresseIAT] |
|
III) APPEL INDIRECT
Call dword ptr [adresse???] |
IV) APPEL INDIRECT RETARDÉ
Mov esi, dword ptr [adresse???] Call esi |
I) APPEL DIRECT PROCHE :
Je lui donne le nom d' "Appel Direct Proche " car pour appeler la
fonction un simple CALL xxxxxxxx suffit.Il est Proche parce que l'adresse en
question reste dans la partie code.
Ce n'est peut-être pas le type de lien le plus courrant mais c'est de
loin le plus simple. Et comme un exemple vaut tous les discours, allons-y gaiement!!
* Reference To: KERNEL32.ExitProcess, Ord:0075h
:00401201 E8400D0000 Call 00401F46 <--
adresse appartenant à la section code
....
....
* Reference To: KERNEL32.ExitProcess, Ord:0075h
:00401F46 FF2514204000 Jmp
dword ptr [00402014] <-- adresse ayant un rapport avec l'IAT
Dans ce type d'utilisation d'une fonction, on utilise un simple "Call xxxxxxxx"
lequel pointe tout en bas de la section .code.
A la fin de la partie .code justement on trouve les différents sauts
qui pointent dans l'IAT sur le membre "First Thunk"
de la fonction en question
Ici il semble bien qu'on ignore la structure IMAGE_IMPORT_DESCRIPTOR qui est
la première normalement consultée. Vous pouvez vous contenter
de ces connaissances sans allez plus loin. C'est suffisant.
Vous voulez le détail pour vraiment comprendre...
Avec (ProcDump : ImageBase = 00400000h VirtualAddress= 00002000h RawAddress = 00001400h |
0042014 - 00400000 = 0000 2014 Et on prend comme référence non plus le VirtualAdress mais le RawOffset soit => 0000 1414h (Parce que HexWorkShop se réfère au RawOffset) |
dans HexWorkShop en 0000 1414 on trouve le dword F422 0000 (Qui est bien le
'First Thunk" de ExitProcess) car en
F422 0000 au format Intel (inversé donne 0000 22F4)
Et en changeant de base on trouve 0000 22F4 - 0000 2000 + 0000 1400 = 0000 16F4
Et en 0016F4 on trouve bien u.ExitProcess.....
(N°Ordinal+Function_Name)
Ca c'est une structure IMAGE_IMPORT_BY_NAME sans le moindre doute. Et Qu'est-ce
qui pointe sur une structure IMAGE_IMPORT_BY_NAME ? c'est toujours la structure
"FIRST_THUNK" de la fonction en question. On vient donc de prouver
que l'adresse ente les crochets pointe belle et bien vers la structure "FIRST_THUNK"
de la fonction qu'on Call. (La boucle est bouclée).
II) APPEL DIRECT LOINTAIN :
Celui-ci est sûrement le type le plus courrant de lien entre la partie
code et l'IAT.
Je l'appelle ainsi par ce que ce call se sert bien des info contenues dans l'IAT
(contrairement aux cas qui vont suivres), mais lointain car cette adresse n'appartient
plus à la section code.
* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00402014]
<-- ayant un rapport direct avec l'IAT.
Ce call là est une concaténation du Call/Jmp dword vu précédemment.
Et l'adresse qu'il contient n'est autre que le RVA FirstThunk si vous prenez
le soin de lui soustraire l'ImageBase.
Reprenez les données du précédent exemple et vous avez
son double. C'est seulement une autre façon de présenter la chose.
III) APPEL INDIRECT :
Je l'appelle indirect parce qu'on ne passe à aucun moment par l'IAT.
Ici on se passe de ses services. Malheureusement, c'est le second type de lien
le plus courrant.
* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00407354]
<-- adresse hors du programme.
Ce type d'appel est particulier. Ici le Call ne pointe pas dans l'IAT. Et comme
dans le cas précédent ce n'est pas l'adresse entre les crochets[]
qui est importante mais ce que contient cette adresse. Ce contenu est lui-même
une autre adresse, qui elle pointe directement dans la DLL à l'endroit
où se trouve la fonction. On ne passe donc pas par l'IAT, on va chercher
la fonction directement dans la DLL.
Exemple complet:
* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00407354]
(avec par exemple [Contenu_de_00407354] = 833F2FB8)
L'adresse 833F2FB8 n'appartient ni au programme lui-même ni à la
DLL (Kernel32.dll) qui contient cette fonction (ExitProcess). On pointe donc
à un endroit inconnu mais qui nous renvoie bien vers la fonction en question.
:833F2FB8 push BFF84DF8
<------------------------ adresse de la fonction
:833F2FBD jmp KERNEL32.BFF957CA
Maintenant seulement Ce jmp nous amène à la fonction tant convoitée.
ExitProcess ( )
:BFF84DF8 mov eax, ......
..................
..........
C'est vrai que cet appel n'utilise pas les services de l'IAT, Cependant il n'est
pas question pour autant d'altérer le nom de cette fonction dans l'IAT.
Car même si le code n'y fait pas appelle, son nom est utilisé à
un autre moment pour connaitre toutes les fonctions importées par le
programme par exemple en passant par le chemin habituel : IMAGE_IMPORT_DESCRIPTOR
=> FIRST THUNK => IMAGE_IMPORT_BY_NAME
IV) APPEL INDIRECT RETARDÉ :
Là ça se complique. Le Call appelant la fonction n'utilise plus
une adresse fixe mais la valeur contenue dans un Registre. C'est donc une autre
mnémonique placée en amont qui doit affecter la valeur correcte
au Registre. Un 'Mov', j'ai jamais rien rencontré d'autre pour l'instant,
mais un Pop serait possible.
* Reference To: USER32.CreateWindowExA, Ord:0050h
|
:00402719 8B3DC4734000 mov edi, dword ptr [004073C4]
:0040271F 6800000080 push 80000000
:00402724 680000CF00 push 00CF0000
:00402729 6888614000 push 00406188
* Possible StringData Ref from Data Obj ->"Notepad"
|
:0040272E 6820604000 push 00406020
:00402733 6A00 push 00000000
:00402735 FFD7 call edi
En réalité cet appel n'utilise pas non plus l'IAT. C'est une version un poil plus évoluée du type de lien vu précédemment (III). Le registre va pointer sur une adresse n'appartenant ni au programme lui-même ni à la DLL dont on appelle la fonction. Ce registre pointe ailleurs. Mais l'adresse contenue dans le Registre va nous rediriger dans la DLL sur l'entrée de la fonction appelée.
Exemple :
Contenu de [004073C4] vaut 834F8108 donc edi = 834F8108
834F8108 push BFF55C71 <------------------------
adresse de la fonction
834F810D jmp KERNEL32.BFF957CA
Ce saut nous redirige comme tout à l'heure vers l'adresse de la bonne
fonction.
CreateWindowExA ( )
:BFF55C71 mov ch, 30
..........
Les Registres utilisés sont principalement EDI et ESI, mais on trouve
aussi EBX parfois. Pour ce qui est des Push précédant l'appel
de la fonction je pense que vous avez devinez ce qu'ils représentent.
Donc j'en dirais pas plus... Bon d'accord, mais juste un indice alors... J'ai
pas tout mis ici, mais il y a encore sept autres Push au dessus de cette partie
de code, ce qui en fait douze au total au dessus de l'appel à la fonction
CreateWindowEx. Voyez plutôt la référence W32API et le nombre
d'arguments de cette fonction justement.
V) RETABLIR LES LIENS ENTRE LE CODE ET LES FONCTIONS :
Revenons au principal problème.
Quel est le but de tout ceci.... Imaginons.... Je souhaite ajouter une DLL et
ses fonctions dans un programme. Pire je souhaite utiliser une fonction (non
présente) dans une DLL qui elle est déjà déclarée.
Dans les deux cas je suis obligé de reconstruire un nouvel IAT, Déplacer
des fonctions pour pouvoir en insérer certaines autres entre elles. Résultat,
les adresses initiales où elles étaient ne sont plus valides.
Maintenant supposons que nous ayons réussit à recréer
un IAT valide avec en plus nos rajouts, et ça, ça n'a rien d'impossible
en fait, j'ai même automatisé cette étape grâce un
un petit programme. Il faut maintenant intervenir dans la section .code elle-même
car beaucoup d'appels ne sont plus correctement adressés.
C'est facile de rediriger les deux premiers types de liens, il suffit de placer
le nouveau "First Thunk" au niveau du "jmp" des fonctions
importées.
Par contre pour les deux derniers types de liens, ça devient chaud.
Le point crussial c'est qu'ils sont indirect, il faud lire le contenu de leurs
adresses. Ca signifie qu'il faut que le programme soit lancé. Par contre
je me suis aperçu que toutes ces adresses contiennent déjà
les bonnes valeurs même si on est toujours sur L'EP.
On peut donc imaginer que le programme qui devra résoudre les liens se
serve des Debug API (en particulier CreateProcess) pour pouvoir le charger en
mémoire mais sans l'exécuté, on resterait sans bouger sur
l'EP. Le point fort, c'est qu'en faisant ça on charge le programme en
mémoire ainsi que les éléments externes qu'il utilise lui-même.
On pourrait alors rechercher les FF15.... Call dword ptr [xxxxxxxx] et cette
fois-ci lire le contenu du xxxxxxxx. Il est donc possible de retrouver les fonctions,
et ainsi remplacer ce type de Call par des Call direct. Mais bon, c'est bien
chaud.... ça demande de vraiment s'inverstir et d'avoir du temps devant
soit.
J'avais fait un programme qui réussissait à résoudre ces
4 types de liens. Mais y en a d'autres plus subtiles encore, et donc au final
c'était l'échèc. Donc pour ma part, et ça ne concerne
que moi, il me sembme que retoucher l'IAT n'est pas une bonne méthode,
d'autant plus que certains chemins détournés peuvent se révélés
plus habiles.